A tracing framework is a collection of software routines and tools that permits a user to record activity of one or more executing programs, including an operating system kernel. Tracing frameworks typically permit users to describe one or more probes to indicate locations in a program that can be used as data-recording sites. The probes are associated with one or more actions that describe what is to be done when the program's execution encounters the probe. Tracing frameworks typically provide a defined set of actions at each probe, a set of user-selectable actions, or the ability to execute an essentially arbitrary set of actions composed in a high-level programming language. In tracing frameworks that support a programming language for describing actions, language statements are compiled into an intermediate form or directly into machine code and are then executed when the probe triggers.
In a tracing framework for an operating system kernel that provides the ability to specify actions using language statements, the set of actions may include the ability to trace data in the memory address space of both the operating system kernel and one or more user process address spaces. The capability to access both user and kernel memory from a tracing action permits users of the tracing framework to observe the state of both kernel and user-mode software simultaneously observing the state allows analysis of the relationship between the states at a given point in time. However, the ability to usefully trace and examine data in memory from either the kernel or a user process requires an understanding of the data layout (i.e., the definition of the various relevant data structures).
For example, the data structure of interest may be the execution stack representing the current control flow of the thread that triggered a particular probe. In order to provide the ability to record a stack (commonly referred to as a stack trace) from either a user or kernel thread, the tracing framework (or its user) utilizes knowledge of the manner in which activation records are allocated on the stack, how to locate the saved program counter address associated with each record (commonly referred to as a stack frame), how to convert these addresses to meaningful names associated with the program's symbol table and source code, etc.
In some instances, the aforementioned details are fixed for a given operating system implementation on a given processor, and thus is readily available for the tracing framework (or user) to determine and use. However, in other instances if the stack trace for a user process is requested and the user process is an interpreter or virtual machine of some kind (e.g., a BASIC interpreter, a LISP interpreter, a Java Virtual Machine (JVM), etc.), then potentially multiple stacks of interest to the user of the tracing framework may exist. For example, the user may be interested in the native stack of the interpreter or virtual machine process (e.g., the stack trace of one of the JVM's threads), the stack of the program that the interpreter or virtual machine is executing (e.g., a stack trace of one of the Java threads executing inside of the JVM), etc.
In the aforementioned scenario, the stack traces are themselves described by a set of data structures that are associated with the implementation of the interpreter or the virtual machine. Accordingly, in order for the tracing framework to effectively record information from such stack traces, the tracing framework (or the user) typically requires knowledge of the data structures of interest in each of the different stack traces.